package pfc.modeloer;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collection;
import java.util.HashMap;

import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.ForeignKey;
import org.apache.ddlutils.model.Reference;
import org.apache.ddlutils.model.Table;
import pfc.comun.IDEpfc;

public class EREntity extends ERElement implements Serializable, Cloneable {

    /** Unique ID for serialization purposes. */
    private static final long serialVersionUID = -5541154961302342608L;
    /** The catalog of this table as read from the database. */
    private String _catalog = null;
    /** The table's schema. */
    private String _schema = null;
    /** The name. */
    private String _name = null;
    /** A desription of the table. */
    private String _description = null;
    /** The table's type as read from the database. */
    private String _type = null;
    /** The columns in this table. */
    private ArrayList _attribs = new ArrayList();
    /** The foreign keys associated to this table. */
    private ArrayList _foreignKeys = new ArrayList();
    /** The indices applied to this table. */
    private ArrayList _indices = new ArrayList();
    private Table _table;
    private String _tableName;
    private ArrayList<ERRelationShip> _relaciones = new ArrayList();
    private ERModel _erModel;
    private HashMap<String, Object> _representativeAttibutes = new HashMap<String, Object>();
    private boolean _generateIdentity=false;

    public EREntity(ERModel m) {
        //System.out.println("Creando entidad");
        _erModel = m;
        this.setErElementType(EntityType.Unknown);
    }

    EREntity(ERModel m, Table t) {
        _erModel = m;
        _name = t.getName();
        _table = t;
        _tableName = t.getName();
        this.setErElementType(EntityType.Unknown);
        init(t);
        checkPKGeneration(t);
    }

    public ERModel getErModel() {
        return _erModel;
    }

    public void setErModel(ERModel _erModel) {
        this._erModel = _erModel;
    }

    public ArrayList<ERRelationShip> getRelationShipList() {
        return _relaciones;
    }

    public void setRelaciones(ArrayList<ERRelationShip> _relaciones) {
        this._relaciones = _relaciones;
    }

    public String getTableName() {
        return _tableName;
    }

    public void setTableName(String _tableName) {
        this._tableName = _tableName;
    }

    public Table getTable() {
        return _table;
    }

    public void setTable(Table _table) {
        this._table = _table;
        for (ERAttrib a : this.getAttribs()) {
            a.setColumn(_table.findColumn(a.getName()));
        }
    }

    void init(Table t) {
        setCatalog(t.getCatalog());
        setDescription(t.getDescription());
        setName(t.getName());
        setSchema(t.getName());
        setType(t.getType());

        for (Column c : t.getColumns()) {
            addAttrib(new ERAttrib(c));

        }
    }

    public String getCatalog() {
        return _catalog;
    }

    public void setCatalog(String catalog) {
        _catalog = catalog;
    }

    public String getSchema() {
        return _schema;
    }

    public void setSchema(String schema) {
        _schema = schema;
    }

    public String getType() {
        return _type;
    }

    public void setType(String type) {
        _type = type;
    }

    public String getName() {
        return _name;
    }

    public void setName(String name) {
        _name = name;
    }

    public String getDescription() {
        return _description;
    }

    public void setDescription(String description) {
        _description = description;
    }

    public int myGetAttribCount() {
        return _attribs.size();
    }

    public ERAttrib myGetAttrib(int idx) {
        return (ERAttrib) _attribs.get(idx);
    }

    public ERAttrib[] getAttribs() {
        return (ERAttrib[]) _attribs.toArray(new ERAttrib[_attribs.size()]);
    }

    public void addAttrib(ERAttrib column) {
        if (column != null) {
            _attribs.add(column);
        }
    }

    public void addAttrib(int idx, ERAttrib column) {
        if (column != null) {
            _attribs.add(idx, column);
        }
    }

    public void addAttrib(ERAttrib previousColumn, ERAttrib column) {
        if (column != null) {
            if (previousColumn == null) {
                _attribs.add(0, column);
            } else {
                _attribs.add(_attribs.indexOf(previousColumn), column);
            }
        }
    }

    public void addAttribs(Collection columns) {
        for (Iterator it = columns.iterator(); it.hasNext();) {
            addAttrib((ERAttrib) it.next());
        }
    }

    public void removeAttrib(ERAttrib column) {
        if (column != null) {
            _attribs.remove(column);
        }
    }

    public void removeAttrib(int idx) {
        _attribs.remove(idx);
    }

    public int getForeignKeyCount() {
        return _foreignKeys.size();
    }

    public int getIndexCount() {
        return _indices.size();
    }

    public boolean hasPrimaryKey() {
        for (Iterator it = _attribs.iterator(); it.hasNext();) {
            ERAttrib column = (ERAttrib) it.next();

            if (column.isPrimaryKey()) {
                return true;
            }
        }
        return false;
    }

    public ERAttrib findAttrib(String name) {
        return findAttrib(name, false);
    }

    public ERAttrib findAttrib(String name, boolean caseSensitive) {
        for (Iterator it = _attribs.iterator(); it.hasNext();) {
            ERAttrib column = (ERAttrib) it.next();

            if (caseSensitive) {
                if (column.getName().equals(name)) {
                    return column;
                }
            } else {
                if (column.getName().equalsIgnoreCase(name)) {
                    return column;
                }
            }
        }
        return null;
    }

    public int myGetAttribIndex(ERAttrib column) {
        int idx = 0;

        for (Iterator it = _attribs.iterator(); it.hasNext(); idx++) {
            if (column == it.next()) {
                return idx;
            }
        }
        return -1;
    }

    public ERAttrib[] getPrimaryKeyAttribs() {
        Collection pkColumns = CollectionUtils.select(_attribs, new Predicate() {

            public boolean evaluate(Object input) {
                return ((ERAttrib) input).isPrimaryKey();
            }
        });

        return (ERAttrib[]) pkColumns.toArray(new ERAttrib[pkColumns.size()]);
    }

    public ERAttrib[] getAutoIncrementAttribs() {
        Collection autoIncrColumns = CollectionUtils.select(_attribs, new Predicate() {

            public boolean evaluate(Object input) {
                return ((ERAttrib) input).isAutoIncrement();
            }
        });

        return (ERAttrib[]) autoIncrColumns.toArray(new ERAttrib[autoIncrColumns.size()]);
    }

    public Object clone() throws CloneNotSupportedException {
        EREntity result = (EREntity) super.clone();

        result._catalog = _catalog;
        result._schema = _schema;
        result._name = _name;
        result._type = _type;
        result._attribs = (ArrayList) _attribs.clone();
        result._foreignKeys = (ArrayList) _foreignKeys.clone();
        result._indices = (ArrayList) _indices.clone();

        return result;
    }

    public boolean equals(Object obj) {
        if (obj instanceof EREntity) {
            EREntity other = (EREntity) obj;

            // Note that this compares case sensitive
            // TODO: For now we ignore catalog and schema (type should be irrelevant anyways)
            return new EqualsBuilder().append(_name, other._name).append(_attribs, other._attribs).append(new HashSet(_foreignKeys), new HashSet(other._foreignKeys)).append(new HashSet(_indices), new HashSet(other._indices)).isEquals();
        } else {
            return false;
        }
    }

    public int hashCode() {
        // TODO: For now we ignore catalog and schema (type should be irrelevant anyways)
        return new HashCodeBuilder(17, 37).append(_name).append(_attribs).append(new HashSet(_foreignKeys)).append(new HashSet(_indices)).toHashCode();
    }

    public String toString() {
        StringBuffer result = new StringBuffer();

        result.append("Entidad [name=");
        result.append(getName());
        result.append("; ");
        result.append(myGetAttribCount());
        result.append(" atributos]");

        return result.toString();
    }

    /**
     * Returns a verbose string representation of this table.
     * 
     * @return The string representation
     */
    public String toVerboseString() {
        StringBuffer result = new StringBuffer();

        result.append("Entidad [name=");
        result.append(getName());
        result.append("; tipo=");
        result.append(this.getErElementType());
        /*
        result.append("; schema=");
        result.append(getCatalog());
        result.append("; type=");
        result.append(getType());
        result.append("] columns:");
        for (int idx = 0; idx < myGetAttribCount(); idx++)
        {
        result.append(" ");
        result.append(myGetAttrib(idx).toVerboseString());
        }
        result.append("; indices:");
        for (int idx = 0; idx < getIndexCount(); idx++)
        {
        result.append(" ");
        }
        result.append("; foreign keys:");
         *
         */

        return result.toString();
    }

    public Collection<ERReferences> buildERReferences(Table t) {
        ArrayList<ERReferences> referencias = new ArrayList<ERReferences>();

        System.out.println("\nAñadiendo referencias de la tabla " + t.getName());
        for (ForeignKey k : t.getForeignKeys()) {
            System.out.println("\tClave Ajena " + k.getName() + " referencia " + k.getForeignTable().getName());

            for (Reference r : k.getReferences()) {

                System.out.println("\t\t" + t.getName() + "." + r.getLocalColumnName() + " -> " + k.getForeignTableName() + "." + r.getForeignColumnName());
            }
        }


        return referencias;
    }

    public ERRelationShip convertIntoRelationShip() {
        ManyToManyRelationShip r = new ManyToManyRelationShip();
        ArrayList<EREntity> tmp = new ArrayList<EREntity>();


        String nombreRelacion = _table.getName();
        //r.addMember(this);
        for (ForeignKey fk : _table.getForeignKeys()) {
            String fkTable = fk.getForeignTableName();
            EREntity fkEntity = this.getErModel().findEntity(fkTable);
            r.addMember(fkEntity);

            nombreRelacion += "-" + fk.getForeignTableName();

            for (Reference ref : fk.getReferences()) {
                ERAttrib sa = this.findAttrib(ref.getLocalColumnName());
                ERAttrib da = fkEntity.findAttrib(ref.getForeignColumnName());
                r.addReference(new ERReference(this, fkEntity, sa, da));

            }

            tmp.add(fkEntity);
        }

        r.setName(_name);
        r.setTableName(_table.getName());

        for (EREntity e : tmp) {
            e.addRelationShip(r);
        }
        this.addRelationShip(r);

        return r;
    }

    public void buildRelationShips() {
        /*
         * Construye las relaciones de esta entidad en base a sus claves ajenas
         * Añade a las entidades referenciadas esta relacion.
         */
        for (ForeignKey fk : _table.getForeignKeys()) {
            String nombre = fk.getForeignTableName() + "-" + _table.getName() + "s";
            EREntity esta = this;
            EREntity otra = this.getErModel().findEntity(fk.getForeignTableName());

            OneToManyRelationShip rel = new OneToManyRelationShip();

            rel.setName(nombre);
            rel.setOneEntity(otra);
            rel.setManyEntity(this);
            rel.setDirectRelationName(fk.getForeignTableName());
            rel.setReverseRelationName(_table.getName());

            for (Reference ref : fk.getReferences()) {
                ERAttrib sa = this.findAttrib(ref.getLocalColumnName());
                ERAttrib da = otra.findAttrib(ref.getForeignColumnName());
                ERReference r = new ERReference(esta, otra, sa, da);
                rel.addReference(r);
            }

            _relaciones.add(rel);
            otra.addRelationShip(rel);
        }
    }

    Collection<ERRelationShip> getRelationShips() {
        return _relaciones;
    }

    public void addRelationShip(ERRelationShip rel) {
        _relaciones.add(rel);
    }

    public Iterable<String> getRepresentativeAttibutes() {
        return _representativeAttibutes.keySet();
    }

    public void addRepresentativeAttribute(String att) {
        this._representativeAttibutes.put(att, att);
    }

    private void checkPKGeneration(Table t) {
        for (Column c : t.getPrimaryKeyColumns()) {
 //           System.out.println("Comprobando columna " + t.getName() + "." + c.getName());
            boolean formaParteFK=false;
            for (ForeignKey key : t.getForeignKeys()) {
                if (key.hasLocalColumn(c))
                    formaParteFK = true;
            }

            if (!formaParteFK){
              if (!c.isAutoIncrement()) {
 //                       System.out.println("\tColumna susceptible de ser generada " + t.getName() + "." + c.getName() );
                        ERAttrib a = this.findAttrib(c.getName());
                        a.setGenerable(true);
                        String val = IDEpfc.getPropertyValue("generarPKs");
                        if (val.equals("si"))
                            this.setGenerateIdentity(true);
//                        System.out.println("\t\t\t\t " + a.isGenerable());
                        break;
                    }
                }
            }
        
    }

    public void setGenerateIdentity(boolean b) {
        this._generateIdentity = b;
    }

    public boolean isGenerateIdentity()
    {
        return this._generateIdentity;
    }
}
